/*
 * Copyright (c) 2011 Norbert Kopcsek
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package org.modulr.service {

	import flash.events.EventDispatcher;
	import flash.utils.Dictionary;

	import mx.collections.ArrayList;
	import mx.collections.IList;

	import org.modulr.bundle.Bundle;
	import org.modulr.bundle.IBundleContext;
	import org.modulr.framework.Framework;

	[Event( name="serviceRegistered", type="org.modulr.service.ServiceEvent" )]
	[Event( name="serviceUnregistered", type="org.modulr.service.ServiceEvent" )]
	[Event( name="serviceUnregistering", type="org.modulr.service.ServiceEvent" )]
	/**
	 * The ServiceRegistry.
	 *
	 * @author Norbert Kopcsek
	 */
	public class ServiceRegistry extends EventDispatcher {


		//------------------------------------------------------------------------------
		//
		//   Constructor 
		//
		//------------------------------------------------------------------------------

		public function ServiceRegistry( framework:Framework ) {
			_services = new ArrayList();
			servicesByClazz = new Dictionary();
			servicesByContext = new Dictionary();
		}


		//------------------------------------------------------------------------------
		//
		//   Protected fields 
		//
		//------------------------------------------------------------------------------

		/**
		 * @private
		 */
		protected var _services:IList;

		/*public function get services():IList
		   {
		   return _services;
		 }*/


		/**
		 * @private
		 */
		protected var servicesByClazz:Dictionary;


		/**
		 * @private
		 */
		protected var servicesByContext:Dictionary;


		//------------------------------------------------------------------------------
		//
		//  Methods 
		//
		//------------------------------------------------------------------------------

		public function registerService( context:IBundleContext, clazz:String, service:Object, properties:Object = null ):IServiceRegistration {
			var registration:IServiceRegistration = new ServiceRegistration( context, clazz, service, properties );

			var contextServices:IList = servicesByContext[ context ] as IList;

			if ( contextServices == null ) {
				contextServices = new ArrayList();
				servicesByContext[ context ] = contextServices;
			}
			contextServices.addItem( registration );

			var clazzServices:IList = servicesByClazz[ registration.clazz ] as IList;

			if ( clazzServices == null ) {
				clazzServices = new ArrayList();
				servicesByClazz[ registration.clazz ] = clazzServices;
			}
			clazzServices.addItem( registration );

			_services.addItem( registration );

			dispatchEvent( new ServiceEvent( ServiceEvent.SERVICE_REGISTERED, registration.reference ));

			return registration;
		}

		public function unregisterService( context:IBundleContext, registration:IServiceRegistration ):void {
			var contextServices:IList = servicesByContext[ context ] as IList;

			if ( contextServices != null ) {
				contextServices.removeItemAt( contextServices.getItemIndex( registration ));
			}

			_services.removeItemAt( _services.getItemIndex( registration ));

			var clazzServices:IList = servicesByClazz[ registration.clazz ] as IList;
			clazzServices.removeItemAt( clazzServices.getItemIndex( registration ));
		}

		public function unregisterServices( context:IBundleContext ):void {
			var serviceRegistrations:IList = servicesByContext[ context ] as IList;

			if ( serviceRegistrations != null ) {
				servicesByContext[ context ] = null;
				delete servicesByContext[ context ];


				for ( var i:int = 0; i < serviceRegistrations.length; i++ ) {
					var serviceRegistration:IServiceRegistration = serviceRegistrations.getItemAt( i ) as IServiceRegistration;
					_services.removeItemAt( _services.getItemIndex( serviceRegistration ));
					var clazzServices:IList = servicesByClazz[ serviceRegistration.clazz ] as IList;
					clazzServices.removeItemAt( clazzServices.getItemIndex( serviceRegistration ));
				}
			}
		}

        /**
         * Returns an array of all registered service class names.
         * @return An array of all registered service class names.
         */
        // TODO Rename to lookUpClassNames
		public function lookupClazzes():Array {
			var result:Array = [];

			for ( var clazz:String in servicesByClazz ) {
				result.push( clazz );
			}
			return result;
		}

        /**
         * Looks for service registrations provided by the given service class name.
         * @param clazz The service class name which we are looking for.
         * @return A list of service registrations.
         */
        // TODO Rename parameter to className
		public function lookupByClazz( clazz:String ):IList {
			var list:IList = servicesByClazz[ clazz ] as IList;
			return list != null ? new ArrayList( list.toArray()) : new ArrayList();
		}

        /**
         * Looks for service registrations provided by the given bundle context.
         * @param context The bundle context which we are looking for.
         * @return A list of service registrations.
         */
		public function lookupByContext( context:IBundleContext ):IList {
			var list:IList = servicesByContext[ context ] as IList;
			return list != null ? new ArrayList( list.toArray()) : new ArrayList();
		}

		/**
		 * Looks for service registrations used by the given bundle context.
		 * @param bundle The bundle which we are looking for.
		 * @return A list of service registrations.
		 */
		public function lookupServicesUsedByBundle( bundle:Bundle ):IList {

			var registeredBundle:Bundle;
			var service:IServiceRegistration;
			var result:IList = new ArrayList();

			for ( var i:int = 0; i < _services.length; i++ ) {
				service = _services.getItemAt( i ) as IServiceRegistration;

				for ( var j:int = 0; j < service.usingBundles.length; j++ ) {
					registeredBundle = service.usingBundles.getItemAt( j ) as Bundle;

					if ( bundle == registeredBundle ) {
						result.addItem( service );
						break;
					}
				}
			}
			return result;
		}
	}
}

